#![allow(dead_code)]

use ash::{
    ext::debug_utils, khr::swapchain::{self, *}, prelude::VkResult, vk::{
        self, make_api_version, AccessFlags, BufferMemoryBarrier, CommandBufferLevel, CommandBufferUsageFlags, CommandPool, DependencyFlags, Extent2D, Extent3D, Handle, Image, ImageAspectFlags, ImageLayout, Offset2D, Offset3D, PFN_vkCreateWin32SurfaceKHR, PipelineStageFlags, QueueFlags, Rect2D, Result, SharingMode, SurfaceKHR, SwapchainKHR
    }, Device, Entry, Instance
};
use getset::{CloneGetters, CopyGetters, Getters, MutGetters, Setters, WithSetters};
use glfw::ffi::glfwWaitEvents;
use std::{
    default,
    ffi::{c_char, CStr, CString},
    mem::*,
    os::raw::*,
    ptr::{null, null_mut},
    sync::{Arc, LazyLock, Mutex, RwLock}, u64,
};
use wgpu::rwh::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
use winit::{dpi::*, platform::windows::*, window::{Window, WindowAttributes}};

use crate::engine::{njm_device::{get_device_proc_addr, get_device_proc_addr_converted, NjmDevice}, njm_swapchain::{NjmSwapChain, MAX_FRAMES_IN_FLIGHT}};

// 
#[derive(Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters)]
pub struct NjmRenderer {
    #[getset(get = "pub")]
    njm_dev: Arc<Mutex<NjmDevice>>,

    #[getset(get = "pub")]
    swap_chain: Arc<Mutex<NjmSwapChain>>,

    #[getset(get = "pub")]
    cmd_buffs: Vec<vk::CommandBuffer>,

    #[getset(get = "pub")]
    current_img_idx: usize,

    #[getset(get = "pub")]
    current_frame_idx: usize,

    #[getset(get = "pub")]
    is_frame_started: bool,
}

impl Drop for NjmRenderer {
    fn drop(&mut self) {
        let dev = self.njm_dev.lock().unwrap();
        unsafe { dev.device().free_command_buffers(*dev.command_pool(), &self.cmd_buffs) };
        self.cmd_buffs.clear();
    }
}

impl NjmRenderer {

    pub fn new(njm_dev: Arc<Mutex<NjmDevice>>, wnd_extent: vk::Extent2D) -> Self {
        let cmd_buffs = Self::create_cmd_buffs(njm_dev.clone());
        let swap_chain = Self::create_swap_chain(njm_dev.clone(), wnd_extent);
        return Self {
            njm_dev: njm_dev,
            cmd_buffs: cmd_buffs,
            swap_chain: swap_chain,
            current_img_idx: 0,
            current_frame_idx: 0,
            is_frame_started: false,
        };
    }

    // init helper
    pub fn create_cmd_buffs(njm_dev: Arc<Mutex<NjmDevice>>) -> Vec<vk::CommandBuffer> {
        let dev_ref = njm_dev.lock().unwrap();
        let cmdbuf_alloc_info = vk::CommandBufferAllocateInfo::default()
            .level(vk::CommandBufferLevel::PRIMARY)
            .command_pool(*dev_ref.command_pool())
            .command_buffer_count(MAX_FRAMES_IN_FLIGHT);
        let rst = unsafe { dev_ref.device().allocate_command_buffers(&cmdbuf_alloc_info).unwrap() };
        return rst;
    }

    pub fn create_swap_chain(
        njm_dev: Arc<Mutex<NjmDevice>>,
        wnd_extent: vk::Extent2D,
    ) -> Arc<Mutex<NjmSwapChain>> {
        assert!(wnd_extent.width != 0 && wnd_extent.height != 0);
        return Arc::new(Mutex::new(NjmSwapChain::new(njm_dev, wnd_extent, None)));
    }

    pub fn update_device_and_swap_chain(
        &mut self, 
        njm_dev: Arc<Mutex<NjmDevice>>, 
        wnd_extent: vk::Extent2D,
    ) {
        assert!(wnd_extent.width != 0 && wnd_extent.height != 0);
        self.njm_dev = njm_dev.clone();
        self.update_swap_chain(wnd_extent);
    }
    pub fn update_swap_chain(
        &mut self, 
        wnd_extent: vk::Extent2D,
    ) { 
        assert!(wnd_extent.width != 0 && wnd_extent.height != 0);
        let previous = Arc::downgrade(&self.swap_chain);
        self.swap_chain = Arc::new(Mutex::new(NjmSwapChain::new(self.njm_dev.clone(), wnd_extent, previous.upgrade())));
    }

    // public api
    pub fn is_frame_in_progress(&self) -> bool { return self.is_frame_started; }
    pub fn get_swapchain_renderpass(&self) -> vk::RenderPass { return *self.swap_chain.lock().unwrap().render_pass(); }
    pub fn get_current_cmdbuf(&self) -> vk::CommandBuffer { 
        assert!(self.is_frame_started, "frame not start yet!");
        return self.cmd_buffs[self.current_frame_idx]; 
    }
    pub fn get_current_frame_index(&self) -> usize {
        assert!(self.is_frame_started, "frame not start yet!");
        return self.current_frame_idx;
    }

    pub fn begin_frame(&mut self) -> vk::CommandBuffer {
        assert!(!self.is_frame_started, "frame already began");
        let sc = &mut self.swap_chain.lock().unwrap();
        sc.set_current_frame(self.current_frame_idx);
        
        let (img_idx, is_suboptimal) = sc.aquire_next_image();
        if is_suboptimal {
            println!("renderer::begin_frame::swap_chain::aquire_next_image : suboptimal result!, may need recreate or update sc");
        }
        self.current_img_idx = img_idx as usize;
        if is_suboptimal {
            // suboptimal, may need recreate or update sc
            println!("swap_chain::aquire_next_image : suboptimal result!");
        }
        self.is_frame_started = true;
        let cmdbuf = self.get_current_cmdbuf();
        let dev = self.njm_dev.lock().unwrap();
        let _ = unsafe { dev.device().reset_command_buffer(cmdbuf, vk::CommandBufferResetFlags::RELEASE_RESOURCES).unwrap() };
        let begin_info = vk::CommandBufferBeginInfo::default();
        let _ = unsafe { dev.device().begin_command_buffer(cmdbuf, &begin_info).unwrap() };
        return cmdbuf;
    }

    pub fn end_frame(&mut self) {
        assert!(self.is_frame_started, "frame not start yet!");
        let cmdbuf = self.get_current_cmdbuf();
        let dev = self.njm_dev.lock().unwrap();
        let _ = unsafe { dev.device().end_command_buffer(cmdbuf).unwrap() };
        let bufs = [cmdbuf];
        let sc = &mut self.swap_chain.lock().unwrap();
        let is_suboptimal = sc.submit_command_buffers(&bufs, self.current_img_idx); // fatal problems
        if is_suboptimal {
            println!("renderer::end_frame::swap_chain::submit_command_buffers : suboptimal result!, may need recreate or update sc");
        }
        self.is_frame_started = false;
        self.current_frame_idx = (self.current_frame_idx + 1) % MAX_FRAMES_IN_FLIGHT as usize;
    }

    pub fn begin_render_pass(&self, cmdbuf: vk::CommandBuffer) {
        assert!(self.is_frame_started, "frame not start yet");
        assert!(cmdbuf == self.get_current_cmdbuf(), "can't begin renderpass on cmdbufs from another frame");
        let dev = self.njm_dev.lock().unwrap();
        let sc = self.swap_chain.lock().unwrap();
        
        let mut clear_value = [
            vk::ClearValue::default(),
            vk::ClearValue::default(),
        ];
        clear_value[0].color = vk::ClearColorValue {
            float32: [0.01, 0.01, 0.01, 1.0]
        };
        clear_value[1].depth_stencil = vk::ClearDepthStencilValue{
            depth: 1.0,
            stencil: 0,
        };
        let begin_info = vk::RenderPassBeginInfo::default()
            .render_pass(*sc.render_pass())
            .framebuffer(sc.frame_bufs()[self.current_img_idx])
            .render_area(Rect2D {
                offset: Offset2D { x:0, y:0 },
                extent: *sc.sc_extent(),
            })
            .clear_values(&clear_value);
        unsafe { dev.device().cmd_begin_render_pass(cmdbuf, &begin_info, vk::SubpassContents::INLINE) };

        let viewport = vk::Viewport::default()
            .x(0.0)
            .y(0.0)
            .min_depth(0.0)
            .max_depth(1.0)
            .width(sc.sc_extent().width as f32)
            .height(sc.sc_extent().height as f32);
        let scissor = vk::Rect2D {
            offset: Offset2D { x: 0, y: 0 },
            extent: *sc.sc_extent(),
        };
        unsafe { dev.device().cmd_set_viewport(cmdbuf, 0, &[viewport]) };
        unsafe { dev.device().cmd_set_scissor(cmdbuf, 0, &[scissor]) };

    }

    pub fn end_render_pass(&self, cmdbuf: vk::CommandBuffer) {
        assert!(self.is_frame_started, "frame not start yet");
        assert!(cmdbuf == self.get_current_cmdbuf(), "can't end renderpass on cmdbufs from another frame");
        let dev = self.njm_dev.lock().unwrap();
        unsafe { dev.device().cmd_end_render_pass(cmdbuf) };
    }

    pub fn get_aspect_ratio(&self) -> f32 {
        return self.swap_chain.lock().unwrap().get_swapchain_aspect_ratio();
    }

}